package com.xliu.cs.openproject.lianliankan.music;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.sound.sampled.*;
import java.io.File;
import java.io.IOException;

// Play a music.
public class Music {
    private static final Logger LOG = LoggerFactory.getLogger(Music.class);

    private boolean suspend = false;
    private final Object suspendLock = new Object();
    private volatile boolean stop = false;
    private final File file;
    private volatile SourceDataLine out;
    private volatile AudioInputStream in;

    public Music(File file) {
        this.file = file;
    }

    private void innerPlay() {
        try {
            AudioInputStream tmpIn = AudioSystem.getAudioInputStream(file);
            AudioFormat format = tmpIn.getFormat();

            // transform audio
            if (format.getEncoding() != AudioFormat.Encoding.PCM_SIGNED) {
                format = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,
                        format.getSampleRate(),
                        16,
                        format.getChannels(),
                        format.getChannels() * 2,
                        format.getSampleRate(),
                        false);
                in = AudioSystem.getAudioInputStream(format, tmpIn);
            } else {
                in = tmpIn;
            }

            // open and start audio
            DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);
            out = (SourceDataLine) AudioSystem.getLine(info);
            out.open(format);
            out.start();

            // read the data from in to write to out
            rawplay(out, in);

        } catch (Exception e) {
            LOG.error("play music failed", e);
        } finally {
            if (out != null) {
                out.drain();
                out.stop();
                out.close();
            }
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    LOG.error("close music failed", e);
                }
            }
        }
    }

    private void rawplay(SourceDataLine out, AudioInputStream din) throws IOException {
        byte[] data = new byte[1024];
        int nBytesRead = 0, nBytesWritten = 0;
        while (nBytesRead != -1 && !stop) {
            // play data each time, the data can not be large for small suspend delay
            nBytesRead = din.read(data, 0, data.length);
            if (nBytesRead != -1) {
                nBytesWritten = out.write(data, 0, nBytesRead);
            }

            // check if suspend, if notified by stop, will never play again.
            synchronized (suspendLock) {
                try {
                    // wait for resume, if set stop is true, will also notify
                    while (suspend) {
                        suspendLock.wait();
                    }
                } catch (InterruptedException e) {
                    LOG.error("interrupt play thread");
                }
            }
        }
        if (nBytesRead != -1) {
            LOG.info("music is stopped");
        }
    }

    // play music syn or async based on constructor parameter ExecutorService
    public void play() {
        innerPlay();
    }

    public void stop() {
        stop = true;
        // in case of suspend when stopping
        resume();
    }

    public void suspend() {
        synchronized (suspendLock) {
            suspend = true;
        }
    }

    public void resume() {
        synchronized (suspendLock) {
            suspend = false;
            suspendLock.notify();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        LOG.info("current work dir is: {}", System.getProperty("user.dir"));

        File musicFile = new File("./res/music/test.mp3");
        if (!musicFile.exists()) {
            throw new IllegalArgumentException("file not exist");
        }

        Music music = new Music(musicFile);
        new Thread(music::play).start();

        LOG.info("start play music");
        LOG.info("current thread size is : {}", Thread.activeCount());
        Thread.sleep(4000);

        LOG.info("suspend play music");
        long timeA = System.nanoTime();
        music.suspend();
        long timeB = System.nanoTime();

        LOG.info("suspend cost time : {} ms", (timeB - timeA) / 1000000.0);
        Thread.sleep(4000);

        LOG.info("resume music");
        timeA = System.nanoTime();
        music.resume();
        timeB = System.nanoTime();

        LOG.info("resume cost time : {} ms", (timeB - timeA) / 1000000.0);
        LOG.info("current thread size is : {}", Thread.activeCount());

        Thread.sleep(4000);

        LOG.info("suspend play music");
        music.suspend();

        Thread.sleep(1000);
        LOG.info("stop play music");
        music.stop();

    }
}